﻿using System;
using System.Collections.Generic;
using CymbergajTouch.Match;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using CymbergajTouch.Components;

namespace CymbergajTouch.Logic
{
    /// <summary>
    /// Manages bonuses.
    /// </summary>
    class BonusManager : GoalHandler
    {
        //Match properties and miscellaneous
        private MatchComponent matchComponent;
        private Puck puck = null;
        private IceRink iceRink = null;
        private Options options;
        private float bonusProbability = 0.33f;
        private Bonus newBonus = null;
        private Bonus activatedBonus = null;
        private double totalGameTime = 0;
        private const int maxBonusDuration = 20 * 1000;

        //XNA
        private SpriteFont bonusFont;
        private Texture2D noteBackgroundTexture;

        //bonus handlers
        private delegate void ActivateBonusHandler();
        private delegate void DeactivateBonusHandler();

        /// <summary>
        /// Bonus type.
        /// </summary>
        private enum BonusType
        {
            BigGoal,
            SmallGoal,
            ExtraPusher,
            BigPusher,
            SmallPusher,
            Gravity
        }

        /// <summary>
        /// Private class that represents bonus.
        /// </summary>
        private class Bonus
        {

            public BonusType bonusType;
            public String Description { get; set; }
            public double EndsAt { get; set; }
            public double PauseTime { get; set; }
            public bool Left { get; set; }
            public ActivateBonusHandler ActivateBonus;
            public DeactivateBonusHandler DeactivateBonus;

            public Bonus(BonusType bonusType, String description, ActivateBonusHandler ActivateBonus, DeactivateBonusHandler DeactivateBonus)
            {
                this.bonusType = bonusType;
                Description = description;
                this.ActivateBonus = ActivateBonus;
                this.DeactivateBonus = DeactivateBonus;
            }
        }

        private List<Bonus> bonuses = new List<Bonus>();

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="matchComponent">match component</param>
        /// <param name="options">match options</param>
        public BonusManager(MatchComponent matchComponent, Options options)
        {
            this.options = options;
            this.matchComponent = matchComponent;
            bonuses.Add(new Bonus(BonusType.BigGoal, "bigger opponent's goal", EnlargeGoal, NormalizeGoal));
            bonuses.Add(new Bonus(BonusType.SmallGoal, "smaller player goal", ReduceGoal, NormalizeGoal));
            bonuses.Add(new Bonus(BonusType.ExtraPusher, "extra pusher for the player", AddExtraPusher, RemoveExtraPusher));
            bonuses.Add(new Bonus(BonusType.BigPusher, "bigger pusher for the player", EnlargePusher, NormalizePusher));
            bonuses.Add(new Bonus(BonusType.SmallPusher, "smaller pusher for opponent", ReducePusher, NormalizePusher));
            bonuses.Add(new Bonus(BonusType.Gravity, "gravity towards opponent's goal", AddGravity, ClearGravity));
        }

        /// <summary>
        /// Sets puck.
        /// </summary>
        /// <param name="puck">puck</param>
        public void SetPuck(Puck puck)
        {
            this.puck = puck;
        }

        /// <summary>
        /// Sets ice rink.
        /// </summary>
        /// <param name="iceRink">ice rink</param>
        public void SetIceRink(IceRink iceRink)
        {
            this.iceRink = iceRink;
        }

        /// <summary>
        /// Sets font for notes informing about bonus opportunity.
        /// </summary>
        /// <param name="bonusFont">bonus font</param>
        public void SetFont(SpriteFont bonusFont)
        {
            this.bonusFont = bonusFont;
        }

        /// <summary>
        /// Sets background texture for bonus notes.
        /// </summary>
        /// <param name="noteBackgroundTexture"></param>
        public void SetTexture(Texture2D noteBackgroundTexture)
        {
            this.noteBackgroundTexture = noteBackgroundTexture;
        }

        public void GoalScored(object sender, bool left)
        {
            if (!options.Bonuses)
            {
                return;
            }

            ProcessBonus(left);
            newBonus = DrawBonus();
            if (newBonus != null)
            {
                puck.ChangeToBonus();
            }
        }

        /// <summary>
        /// Invokes goal enlargement.
        /// </summary>
        private void EnlargeGoal()
        {
            iceRink.ResizeGoal(0.66f, newBonus.Left);
        }

        /// <summary>
        /// Invokes goal reduction.
        /// </summary>
        private void ReduceGoal()
        {
            iceRink.ResizeGoal(0.25f, !newBonus.Left);
        }

        /// <summary>
        /// Invokes goal normalization.
        /// </summary>
        private void NormalizeGoal()
        {
            bool left = activatedBonus.bonusType == BonusType.BigGoal ? activatedBonus.Left : !activatedBonus.Left;
            iceRink.ResizeGoal(0.33f, left);
        }

        /// <summary>
        /// Invokes extra pusher addition.
        /// </summary>
        private void AddExtraPusher()
        {
            matchComponent.AddExtraPusher(!newBonus.Left);
        }

        /// <summary>
        /// Invokes extra pusher removal.
        /// </summary>
        private void RemoveExtraPusher()
        {
            matchComponent.RemoveExtraPusher();
        }

        /// <summary>
        /// Invokes pusher enlargement.
        /// </summary>
        private void EnlargePusher()
        {
            matchComponent.EnlargePusher(!newBonus.Left);
        }

        /// <summary>
        /// Invokes pusher reduction.
        /// </summary>
        private void ReducePusher()
        {
            matchComponent.ReducePusher(newBonus.Left);
        }

        /// <summary>
        /// Invokes pusher normalization.
        /// </summary>
        private void NormalizePusher()
        {
            bool left = activatedBonus.bonusType == BonusType.BigPusher ? !activatedBonus.Left : activatedBonus.Left;
            matchComponent.NormalizePusher(left);
        }

        /// <summary>
        /// Invokes gravity addition.
        /// </summary>
        private void AddGravity()
        {
            matchComponent.AddGravity(newBonus.Left);
        }

        /// <summary>
        /// Invokes gravity clearance.
        /// </summary>
        private void ClearGravity()
        {
            matchComponent.ClearGravity();
        }

        /// <summary>
        /// Draws bonus.
        /// </summary>
        /// <returns>New bonus if drawn, otherwise null value</returns>
        private Bonus DrawBonus()
        {
            if (bonuses.Count <= 0)
            {
                return null;
            }

            Random random = new Random();

            //draw if bonus should appear
            if (random.NextDouble() >= bonusProbability)
            {
                return null;
            }

            //draw what bonus should appear
            int bonusId = random.Next(bonuses.Count);
            return bonuses[bonusId];
        }

        /// <summary>
        /// Process new and activated bonus.
        /// </summary>
        /// <param name="left">side that bonus belongs to</param>
        private void ProcessBonus(bool left)
        {
            if (activatedBonus != null)
            {
                activatedBonus.DeactivateBonus();
                activatedBonus = null;
            }

            if (newBonus != null)
            {
                newBonus.Left = left;
                newBonus.ActivateBonus();
                newBonus.EndsAt = totalGameTime + maxBonusDuration;
                newBonus.PauseTime = 0;
                activatedBonus = newBonus;
                newBonus = null;
                puck.ChangeToNormal();
            }
        }

        /// <summary>
        /// Checks for bonus that should be deactivated and deactivates if any.
        /// </summary>
        private void CheckForOldBonus()
        {
            if (activatedBonus != null)
            {
                if (totalGameTime >= (activatedBonus.EndsAt + activatedBonus.PauseTime))
                {
                    activatedBonus.DeactivateBonus();
                    activatedBonus = null;
                }
            }
        }

        /// <summary>
        /// Draws bonus notes.
        /// </summary>
        /// <param name="spriteBatch">sprite batch</param>
        public void Draw(SpriteBatch spriteBatch)
        {
            if (newBonus != null)
            {
                String bonusNote = "Next goal = " + newBonus.Description;
                Vector2 noteDimensions = bonusFont.MeasureString(bonusNote);
                Vector2 notePosition = new Vector2((IceRink.iceWidth - noteDimensions.X) / 2, 7 * IceRink.iceHeight / 8);
                Rectangle noteDestinationRectangle = new Rectangle((int)notePosition.X, (int)notePosition.Y, (int)noteDimensions.X, (int)noteDimensions.Y);
                spriteBatch.Draw(noteBackgroundTexture, noteDestinationRectangle, new Color(246, 240, 54, 200));
                spriteBatch.DrawString(bonusFont, bonusNote, notePosition, Color.Black);
            }
        }

        /// <summary>
        /// Updates activated bonus state, taking into consideration potential game pause.
        /// </summary>
        /// <param name="gameTime">game time</param>
        /// <param name="paused">value that indicates if game is paused</param>
        public void Update(GameTime gameTime, bool paused)
        {
            if (paused && activatedBonus != null)
            {
                activatedBonus.PauseTime += gameTime.ElapsedGameTime.TotalMilliseconds;
            }
            totalGameTime = gameTime.TotalGameTime.TotalMilliseconds;
            CheckForOldBonus();
        }
    }
}
